/*----------------------------------------------------------------------------
Owner(s): Shane Whitfield
$Copyright: $
$Id: $
----------------------------------------------------------------------------*/

#include "camera.h"
#include "timermgr.h"
#include <OpenGLES/ES1/gl.h>
#include "world.h"
#include "mathutils.h"
#include "touchmgr.h"

#include "render.h"

/*--------------------------------------------------------------------------*/

CCamera::CCamera() : super("Camera")
{
	m_smoothSpeed = 10;
	SetPos(Vector2f::Zero);
	m_zoomFactor = m_zoomTarget = m_lastRequestedZoom = 1.0f;
	m_tLastMoved = 0.0f;
    m_viewExtents.x = 2.0f;
    m_viewExtents.y = m_viewExtents.x * 1.5f;
	
	m_minZoomFactor = 0.75f;
	m_maxZoomFactor = 2.5f;
}

void CCamera::Apply()
{
	glLoadIdentity();
	
    float screenWidth = GetWorldWidth() * 0.5f;
    float screenHeight = GetWorldHeight() * 0.5f;
    
	float cameraLeft    = -screenWidth + m_worldMat.GetPos().x;
	float cameraRight   = screenWidth + m_worldMat.GetPos().x;
	float cameraBottom  = screenHeight + m_worldMat.GetPos().y;
	float cameraTop     = -screenHeight + m_worldMat.GetPos().y;
	
    glOrthof(cameraLeft, cameraRight, cameraBottom, cameraTop, -1.0f, 1.0f);
}

Vector2f CCamera::ScreenToCameraSpace(const Vector2f& i_screenCoords)
{
	static unsigned int width = 320;
	static unsigned int height = 480;
	
    // - transform into view
    float x = ((i_screenCoords.x*(GetWorldWidth())) / (width)) - (GetWorldWidth() * .5f);
    float y = ((i_screenCoords.y*(GetWorldHeight())) / (height)) - (GetWorldHeight() * .5f);
    
    return Vector2f(x, y);
}

float CCamera::GetCameraStillDuration() const 
{
	return T() - m_tLastMoved;
}

void CCamera::setState(EState i_state)
{
	m_state = i_state;
	
	switch(m_state)
	{
	case EState_ZoomToTatical:
		{
			m_zoomTarget = m_maxZoomFactor;
		}
		break;
	case EState_ZoomToFrame :
		{
			m_zoomTarget = m_lastRequestedZoom;
			m_targetPos = g_touchmgr.GetTouchEventWorldPos(ETouchType_DoubleTapStart);
		}
		break;
	}
}

void CCamera::OnPostWorldLoad()
{
	//set the camera back to the origin
	m_targetPos = Vector2f::Zero;
	m_worldMat.SetPos(Vector2f::Zero);
	
	//reset the zoom for the start of the level
	m_zoomFactor = m_lastRequestedZoom = m_zoomTarget = 1.0f;
}

void CCamera::OnPreWorldUnload()
{
	//set the camera back to the origin
	m_targetPos = Vector2f::Zero;
	m_worldMat.SetPos(Vector2f::Zero);
	
	//reset the zoom for the map
	m_zoomFactor = m_lastRequestedZoom = m_zoomTarget = 1.0f;
}

bool CCamera::smoothToTargetZoom()
{
	bool isAtTarget = true;
	//near the zoom?
	if (!IsNear(m_zoomFactor, m_zoomTarget))
	{
		//calculate the rate of movement this frame
		float zoomDelta = 9.0f * Dt();
		if (m_zoomFactor > m_zoomTarget)
		{
			zoomDelta = -zoomDelta;
		}
		
		//Low frequency filter to give smoothness
		float newZoom = m_zoomFactor + zoomDelta;
		m_zoomFactor = (0.3f * m_zoomFactor) + (0.7f * newZoom);
		
		//clamp to ensure we didn't go pass the mark
		if (zoomDelta < 0)
		{
			m_zoomFactor = BBMAX(m_zoomFactor, m_zoomTarget);
		}
		else
		{
			m_zoomFactor = BBMIN(m_zoomFactor, m_zoomTarget);
		}
		
		isAtTarget = IsNear(m_zoomFactor, m_zoomTarget);
	}
	
	return isAtTarget;
}

bool CCamera::smoothToTargetPos()
{
	bool isAtTarget = true;
	//if we are at the target.. then smooth towards it
	if (!IsNear(GetPos(), m_targetPos))
	{
		//smooth a little bit more towards the position
		Vector2f moveDir = m_targetPos - GetPos();
		
		moveDir.LimitToLength(SystemDt() * m_smoothSpeed);
		
		m_tLastMoved = T();
		m_worldMat.SetPos(GetPos() + moveDir);
		
		isAtTarget = IsNear(GetPos(), m_targetPos);
	}
	
	return isAtTarget; 
}

void CCamera::OnPostWorldUpdate()
{	
	for (;;)
	{
		EState prevState = m_state;
		switch (m_state)
		{
		case EState_Cutting:
			{
				//once we cut we go right back to smoothing
				setState(EState_Smoothing);
			}
			break;
		case EState_ZoomToTatical:
			{
				bool atTarget = smoothToTargetZoom();
				if (atTarget)
				{
					//once there is another double tap... go frame it
					if (g_touchmgr.HasTouchEvent(ETouchType_DoubleTapStart))
					{
						setState(EState_ZoomToFrame);
					}
				}
			}
			break;
		case EState_ZoomToFrame:
			{
				bool atTargetZoom = smoothToTargetZoom();
				bool atTargetPos = smoothToTargetPos();
				if (atTargetZoom && atTargetPos)
				{
					setState(EState_Smoothing);
				}
			}
			break;
		case EState_Smoothing:
			{
				//if there is no touch
				if (!g_touchmgr.HasContact())
				{
					clampTarget();
				}
				
				//handle input
				if (g_touchmgr.HasTouchEvent(ETouchType_DoubleTapStart))
				{
					setState(EState_ZoomToTatical);
				}
				else if (g_touchmgr.HasTouchEvent(ETouchType_DoubleTouchStart))
				{		
					Vector2f touchWorldVec = g_touchmgr.GetTouchEventWorldPos2(ETouchType_DoubleTouchStart) - 
										g_touchmgr.GetTouchEventWorldPos(ETouchType_DoubleTouchStart);
					Vector2f touchScreenVec = g_touchmgr.GetTouchEventScreenPos2(ETouchType_DoubleTouchStart) - 
										g_touchmgr.GetTouchEventScreenPos(ETouchType_DoubleTouchStart);
					m_startTouchWorldVecLength = touchWorldVec.Length();
					
					//keep the length
					m_lastMidScreenPos = g_touchmgr.GetTouchEventScreenPos(ETouchType_DoubleTouchStart) + (touchScreenVec * 0.5f);
				}
				else if (g_touchmgr.HasTouchEvent(ETouchType_DoubleTouchMoving))
				{
					//get the vector between the two touch positions
					Vector2f touchWorldVec = g_touchmgr.GetTouchEventWorldPos2(ETouchType_DoubleTouchMoving) - 
										g_touchmgr.GetTouchEventWorldPos(ETouchType_DoubleTouchMoving);
					Vector2f touchScreenVec = g_touchmgr.GetTouchEventScreenPos2(ETouchType_DoubleTouchMoving) - 
										g_touchmgr.GetTouchEventScreenPos(ETouchType_DoubleTouchMoving);
					float touchWorldVecLength = touchWorldVec.Length();
					Vector2f newMidScreenPoint = g_touchmgr.GetTouchEventScreenPos(ETouchType_DoubleTouchMoving) + (touchScreenVec * 0.5f);
					
					//calculate the new zoom
					float newZoom = m_zoomFactor * m_startTouchWorldVecLength / touchWorldVecLength;
					
					//Low Frequency Filter
					m_zoomFactor = (0.3f * m_zoomFactor) + (0.7f * newZoom);
					
					//clamp to the min/max
					m_zoomFactor = BBMIN(BBMAX(m_zoomFactor, m_minZoomFactor), m_maxZoomFactor);
					
					//store this as the last requested zoom factor
					m_lastRequestedZoom = m_zoomTarget = m_zoomFactor;
					
					//translate the camera over
					Vector2f difVec = ScreenToCameraSpace(newMidScreenPoint) - ScreenToCameraSpace(m_lastMidScreenPos); 
					SetPos(GetPos() - difVec);
					
					m_lastMidScreenPos = newMidScreenPoint;
				}
				
				smoothToTargetPos();
				
			}
			break;
		}
		
		if (prevState == m_state)
		{
			break;
		}
	}
}

void CCamera::SetPos(const Vector2f& i_pos)
{
	Vector2f newPos = i_pos;
	
	float halfWidth = GetWorldWidth() * .5f;
	float halfHeight = (GetWorldHeight() * .5f)- .25f; //remember the UI
     
	newPos.x = BBMIN(BBMAX(newPos.x, g_world.GetAbsMin().x + halfWidth), g_world.GetAbsMax().x - halfWidth);
	newPos.y = BBMIN(BBMAX(newPos.y, g_world.GetAbsMin().y + halfHeight), g_world.GetAbsMax().y - halfHeight);
	
	m_targetPos = newPos;
	m_worldMat.SetPos(newPos);
	setState(EState_Cutting);
	
	m_tLastMoved = T();
}

void CCamera::SetTargetPos(const Vector2f& i_pos)
{
	m_targetPos = i_pos;
	
	clampTarget();
}

void CCamera::clampTarget()
{
	//the target position can never go outside the min/max
	float halfWidth = GetWorldWidth() * .5;
	float halfHeight = (GetWorldHeight() *.5) - .25f; //remember the UI
	m_targetPos.x = BBMIN(BBMAX(m_targetPos.x, g_world.GetMin().x + halfWidth), g_world.GetMax().x - halfWidth);
	m_targetPos.y = BBMIN(BBMAX(m_targetPos.y, g_world.GetMin().y + halfHeight), g_world.GetMax().y - halfHeight);	
}


/*-----------------------------------EOF!-----------------------------------*/ 